<?php 
/**
 * This file is part of DomMonitor.
 * 
 * DomMonitor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * DomMonitor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with DomMonitor.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @link http://inditel.ee/
 * @copyright 2007-2009 Inditel Meedia OÜ
 * @author Oliver Leisalu <oliver@inditel.ee>
 */

/**
* To use this tool just require DomMonitor.init.php
* {@code 
require('libs/DomMonitor.init.php');
DomMonitor::gap('init', 'myGroup' );
// do something
DomMonitor::gap('doSomething', 'myGroup' );
}
*
* @author Oliver Leisalu, Inditel Meedia OÜ
* @package DomMonitor
*/
class DomMonitor {

	private static $scriptStartTime;
	private static $gaps;
	private static $gapLocks;
	
	private static $traces;
	
	private static $self;
	private static $displayed = false;
	private static $logged = false;
	private static $logPrepared = false;
	private static $logId;
	
	private static $location;
	
	/**
	* @param String $style Style dir name. Styles are held in DomMonitor/Templates/ category!
	*/
	public static $style = 'Default';
	
	
	private static $entries = array();
	
	
	private function __consturct() {}
	
	public function __destruct() {
		if( $_SESSION['debugMode'] === true )
			self::display();
		
		else if( DOM_MONITOR_LOG === true )
			self::log( self::explain() );
	}
	
	public function instance() {	
		return self::$self;
	}
	
	public static function init( $scriptStartTime = NULL ) {
		self::$self = new DomMonitor();
		self::$traces = array('trace'=>array());
		self::$gaps = array();
		self::$gapLocks = array();
		self::$scriptStartTime = $scriptStartTime === NULL ? microtime(true) : $scriptStartTime;
		$GLOBALS['DomMonitorActive'] = true;
		
	}

	public static function databaseConnected() {
		self::prepareLog();
	}
	

	public static function register( DomMonitorEntry $entry ) {
		self::$entries[$entry->group][] = $entry;
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	/**
	* @param String $name
	* @param String $group
	* @param Int $gapLockNr
	* @param Int $time
	*/
	public static function gap( $name, $group = 'default', $gapLockNr = 0, $time = NULL ) {
		if( self::$gapLocks[$group] == $gapLockNr ) {
			self::$gaps[$group][$name] = $time == NULL ? microtime(true) : $time;
			self::$gapLocks[$group] = 0; 
		}
	}
	/**
	* Locking is useful for benching recursive methods. First level of recursive method shoult but lock on gap group and then other levels wont be logged.
	* Using locking:
	* {@code 
	DomMonitor::gap('Some gap','foo');
	// locking gap group foo
	$lock = DomMonitor::lockGap('foo');
	// this wont be logged, because group foo is locked.
	DomMonitor::gap('cc','foo');
	// adding lock nr will unlonk group and log this entry.
	DomMonitor::gap('bb','foo', $lock);
	}
	* @param String $group gap group name
	*/
	public static function lockGap( $group = 'default' ) {
		if( self::$gapLocks[$group] == 0 ) {
			self::$gapLocks[$group] = mt_rand(1,999999999);
			return self::$gapLocks[$group];
		} else
			return 0;
	}

	
	
	
	
	/**
	* Send message to bench message board. Useful for debugging.
	* @param String $data
	* @param String $group
	*/
	public static function trace( $data, $group = 'trace' ) {
		ob_start();
		if( is_object($data) === true or is_array($data) === true ) {
			echo '<pre>';
			print_r($data);
			echo '</pre>';
		} else{
			echo $data;
		}
			
		$res = array();
		$res['value'] = ob_get_contents();
		ob_end_clean();
		
		try {
			throw new Exception();
		
		} catch( Exception $e ) {
			$trace  = $e->getTrace();
			$desc = array_shift($trace);

			
			if( $trace[0]['function'] == 'trace' ) 
				$desc = array_shift($trace);

			$res['info'] = $desc['file'].' on line '.$desc['line'].'';
		}
		
		self::$traces[$group][] = $res;
		
		
	}
	
	
	
	
	private static function formatNumbers( &$array ) {
		foreach( $array as &$value ) {
			if( is_array($value) === true) {
				self::formatNumbers( $value );
			} else if( is_float($value) ) {
				$decimals = 0;
				if( $value > 100 )
					$decimals = 0;
				else if( $value > 10 )
					$decimals = 1;
				else if( $value > 1 )
					$decimals = 2;
				else if( $value > 0.1 )
					$decimals = 3;
				else if( $value > 0.01 )
					$decimals = 4;
				else if( $value > 0.001 )
					$decimals = 5;
				else if( $value > 0.0001 )
					$decimals = 6;
				else if( $value > 0.00001 )
					$decimals = 7;
				else if( $value > 0.000001 )
					$decimals = 8;
						
				$value = number_format( $value, $decimals, '.', '');
			}
		}
	}
	
	public static function setLocation( $controller, $method ) {
		self::$location = array($controller, $method );
	}
	/**
	* Displays bench info.
	* Will be called automatically from __destruct()
	* @param Int $scriptEndTime
	*/
	public static function display( $scriptEndTime = NULL ) {
		$time = microtime(true);
		if( self::$displayed == false ) {
			self::$displayed = true;
			$data = self::explain( $scriptEndTime );
			$logTimeData = self::log( $data );
			$monitorCreateTime = round( microtime(true)-$time,5);
			require( dirname(__FILE__) . '/Templates/'.self::$style.'/monitorResult.php');
		}
	}
	
	
	
	
	/**
	 * Searches for times that contain another times. Meaning that something was done during something else.
	 *
	 * @param array $list
	 * @return Array
	 */
	private static function repairDomMonitorEntries( array $list ) {

		$index = array();
		$times = array();
		foreach( $list as $item ) {
			$index[] = $item;
			$times[] = array('start' => $item->start, 'end' => $item->end, 'description' => $item->description );
		}
		
		$result = DomMonitorTimeTree::calculate( $times );
		foreach( $index as $i => &$item ) {
			$item->setTotalTime( $result[$i]['totalTime'] );
		}

		return $index;
	}
	
	private static function explain( $scriptEndTime = NULL ) {
		if( $scriptEndTime === NULL ) $scriptEndTime = microtime(true);
 		
		// --------------------------
		// Trace out some constants
		trace( '<strong>DOM_ADMIN_EMAIL</strong>: '.DOM_ADMIN_EMAIL.'', 'Dom');
		trace( '<strong>DOM_RUN_ID</strong>: '.DOM_RUN_ID.'', 'Dom');
		trace( '<strong>SITE_PATH</strong>: '.SITE_PATH.'', 'Dom');
		trace( '<strong>DOM_URL</strong>: '.DOM_URL.'', 'Dom');
		trace( '<strong>DOM_PATH</strong>: '.DOM_PATH.'', 'Dom');
		trace( '<strong>DOM_MONITOR_LOG</strong>: '.(DOM_MONITOR_LOG ? 'true' : 'false').'', 'Dom');
		// -----------------
		
		$data = array(
			'overall'=>array(),
			'libs'=>array(),
			'gaps'=>array(),
		);
		
		
		//debug(self::$gaps);
		//debug(Dom::$monitorData);
		
		$data['overall']['totalTime'] = $scriptEndTime - self::$scriptStartTime;
		$data['overall']['percent'] = 100;
		$data['overall']['freeTime'] = $data['overall']['totalTime'];
		$data['overall']['freePercent'] = 100;
		
		$data['overall']['tracedMessages'] = 0;
		foreach( self::$traces as $trace ) {
			$data['overall']['tracedMessages'] += count($trace);
		}
		
		// Libs
		$libs = array();

		foreach( get_declared_classes() as $class ) {
			$implements = class_implements($class);
			if( isset($implements['DomMonitorable']) or $class == 'Dom' ) {
				
				$libs[] = $class;
				$res = array();
				$res['overallFields'] = array('count'=>'Queries');
				
				foreach( self::repairDomMonitorEntries( (array)self::$entries[$class] ) as $dat ) {
					$res['totalTime'] += $dat->totalTime;
					$res['count']++;
					$res['data'][] = array('end'=>$dat->end, 'start'=>$dat->start, 'value' => $dat->description, 'totalTime'=>$dat->totalTime );
				}
				
				$res['percent'] = 100;
				$res['percentFromTotal'] = $data['overall']['totalTime'] > 0 ? $res['totalTime'] * 100 / $data['overall']['totalTime'] : 0;
				
				foreach( (array)$res['data'] as $i => $dat ) {
					$res['data'][$i]['percent'] = $res['totalTime'] > 0 ? $dat['totalTime'] * 100 / $res['totalTime'] : 0;
				}
				$data['libs'][$class] = $res;
				$data['overall']['freeTime'] -= $data['libs'][$class]['totalTime'];
				$data['overall']['freePercent'] -= $data['libs'][$class]['percentFromTotal'];
			}
		}
		
		// Gaps
		foreach( self::$gaps as $group => $gaps ) {
			$prevGap = self::$scriptStartTime;
			$gaps['to end'] = $scriptEndTime;
			foreach( $gaps as $name => $gapEnd ) {
				$dat = array();
				$dat['name'] = $name;
				$dat['totalTime'] = $gapEnd - $prevGap;
				$dat['freeTime'] = $dat['totalTime'];
				$dat['freePercent'] = 100;
				$dat['percent'] = $dat['totalTime'] * 100 / $data['overall']['totalTime'];
				$dat['percentFromTotal'] = $dat['percent'];
				
				$dat['libs'] = array();
				foreach( $libs as $lib ) {
					$dat['libs'][$lib] = array();
					$dat['libs'][$lib]['totalTime'] = 0;
					foreach( (array)$data['libs'][$lib]['data'] as $ben ) {
						if( $ben['start'] > $prevGap && $ben['start'] < $gapEnd ) {
							$dat['libs'][$lib]['data'][] = $ben;
							$dat['libs'][$lib]['totalTime'] += $ben['totalTime'];
						}
					}
					$dat['freeTime'] -= $dat['libs'][$lib]['totalTime'];
					$dat['libs'][$lib]['percent'] = $dat['totalTime'] <= 0 ? 0 : $dat['libs'][$lib]['totalTime'] * 100 / $dat['totalTime'];
					$dat['freePercent'] -= $dat['libs'][$lib]['percent'];
				}

				$data['gaps'][$group][] = $dat;
				$prevGap = $gapEnd;
			}
		}
		self::formatNumbers( $data );
		
		$data['traces'] = self::$traces;
		return $data;
	}
	
	
	
	
	private static function prepareLog( ) {
		
		if( self::$logPrepared == true ) {
			return;
		}
		self::$logPrepared = true;
		
		if( !defined('DOM_MONITOR_LOG') or !DOM_MONITOR_LOG )
			return;
		
		// ----------------------------------------
		// LOG REQUEST AND OVERALL
		$queryString = $_SERVER['QUERY_STRING'];
		if( substr($queryString,0,5) == 'args=' )
			$queryString = substr($queryString,5);
		

		$query = 'INSERT INTO dom_monitor_overall_log (time,ip,sessionId, uri, domain, referer, queryString, finished, error)
					VALUES (
						NOW(), 
						\''.addslashes(ip()).'\',
						\''.addslashes(session_id()).'\',
						\''.addslashes($_SERVER['SCRIPT_FILENAME'].'?'.$_SERVER['QUERY_STRING']).'\',
						\''.addslashes($_SERVER['HTTP_HOST']).'\',
						\''.addslashes($_SERVER['HTTP_REFERER']).'\',
						\''.addslashes($queryString).'\',
						0,
						0
					)';
		
		try {
			DomSql::query( $query );
		} catch( Exception $e ) {
			DomSql::query('CREATE TABLE dom_monitor_overall_log (
				id int(11) PRIMARY KEY AUTO_INCREMENT,
				finished tinyint(1),
				error tinyint(1),
				time datetime,
				ip char(15),
				sessionId char(255),
				uri char(255),
				domain char(255),
				referer char(255),
				controller char(255),
				method char(255),
				queryString char(255),
				loadTime float(10,5),
				sqlTime float(10,5),
				sqlQueries int(11),
				requireTime float(10,5),
				requires int(11),
				userTime float(10,5),
				errorStr longtext
			)');
			DomSql::query( $query );
		}
		self::$logId = DomSql::lastInsertId();
	}
	
	private static function log( array $data ) {
		
		if( self::$logged == true ) {
			return;
		}
		self::$logged = true;
		
		if( !defined('DOM_MONITOR_LOG') or !DOM_MONITOR_LOG )
			return array('time' => 0);

		$time = microtime(true);
			
		// ----------------------------------------
		// LOG REQUEST AND OVERALL
		$queryString = $_SERVER['QUERY_STRING'];
		if( substr($queryString,0,5) == 'args=' )
			$queryString = substr($queryString,5);
		
		$query = 'UPDATE dom_monitor_overall_log SET
			finished = 1,
			controller = \''.addslashes(self::$location[0]).'\',
			method = \''.addslashes(self::$location[1]).'\',
			loadTime = '.(float)$data['overall']['totalTime'].',
			sqlTime = '.(float)$data['libs']['DomSql']['totalTime'].',
			sqlQueries = '.(float)$data['libs']['DomSql']['count'].',
			requireTime = '.(float)$data['libs']['Dom']['totalTime'].',
			requires = '.(float)$data['libs']['Dom']['count'].',
			userTime = '.(float)$data['overall']['freeTime'].',
			error = '.(Dom::$lastError ? '1' : '0').',
			errorStr = \''.addslashes(Dom::$lastError).'\'
			WHERE id = '.(int)self::$logId.'
		';
		DomSql::query( $query );
		
		// ----------------------------------------
		// LOG SQL
		
		$query  = 'INSERT INTO dom_monitor_sql_log (time,overallLogId, queryTime, query) VALUES';
		
		foreach( (array)$data['libs']['DomSql']['data'] as $row ) {
			$query .= '(
							NOW(), 
							'.(int)self::$logId.',
							'.(float)$row['totalTime'].',
							\''.addslashes($row['value']).'\'
						),';
		}
		$query = substr($query,0,-1);
		
		if( count((array)$data['libs']['DomSql']['data']) > 0 ) {
			try {
				DomSql::query( $query );
			} catch( Exception $e ) {
				DomSql::query('CREATE TABLE dom_monitor_sql_log (
					id int(11) PRIMARY KEY AUTO_INCREMENT,
					time datetime,
					overallLogId int(11),
					queryTime float(10,5),
					query longtext
				)');
				DomSql::query( $query );
			}
		}
		$logTime = microtime(true)-$time;
		return array('time' => round($logTime,4));
	}
	
}

?>